package ru.ifmo.cppsc.syntactic;

import ru.ifmo.cppsc.grammar.Symbol;
import ru.ifmo.cppsc.grammar.Production;
import ru.ifmo.cppsc.grammar.AttributedSymbol;
import ru.ifmo.cppsc.grammar.CompoundProduction;

import java.util.ArrayList;
import java.util.Stack;


/**
 * Author: Olga Komaleva
 * Date: Jun 22, 2008
 */
public class LRParser {

    public interface TransactionFunctions {
        public String shift(Stack<Symbol> stack, Symbol terminal);
        public Production reduce(Stack<Symbol> stack, Symbol terminal);
    }

    private final TransactionFunctions funcs;


    public LRParser(TransactionFunctions funcs) {
        this.funcs = funcs;
    }

    public TreeNode parse(ArrayList<AttributedSymbol> s) {
        assert s.size() > 1;
        TreeNode root = new TreeNode(s.get(0), null, null, null);
        Stack<Symbol> stack = new Stack<Symbol>();
        Stack<TreeNode> stack1 = new Stack<TreeNode>();

        int i = 1;
        stack.push(s.get(0).getSymbol());


        while (i < s.size()) {
            if (funcs.shift(stack, s.get(i).getSymbol()).equals("shift")) {

                stack.push(s.get(i).getSymbol());
                stack1.push(new TreeNode(s.get(i), null, null, null));
                i++;
            } else if (funcs.shift(stack, s.get(i).getSymbol()).equals("reduce")) {
                root = reduce(funcs.reduce(stack, s.get(i).getSymbol()), stack, stack1);

            } else if (funcs.shift(stack, s.get(i).getSymbol()).equals("accept")) {

                System.out.println("Accepted");
                return root;

            } else {
                System.out.println("Error string");
                System.out.println("Stack");
                for (Symbol s1 : stack) {
                    System.out.print(s1.getName() + " ");
                }
                System.out.println("");
                System.out.println("Word");
                for (int j = i; j < s.size(); j++) {
                    System.out.print(s.get(j).getSymbol().getName() + " ");
                }
                System.out.println("");
                //assert false;
                return null; //new TreeNode(s.get(i), null, null, null);
            }
        }

        return root;

    }

    private TreeNode reduce(Production p, Stack<Symbol> stack, Stack<TreeNode> stack1) {
        p.print();
        Symbol[] right = p.getRight();
        Stack<TreeNode> children = new Stack<TreeNode>();
        TreeNode parent = new TreeNode(new AttributedSymbol(p.getLeft()), children, null, p.get());
        TreeNode parent1 = parent;
        TreeNode[] nodes = new TreeNode[right.length];

        for (int i = 0; i < right.length; i++) {
            stack.pop();
            nodes[right.length - i - 1] = stack1.pop();
        }
        stack.push(p.getLeft());
        stack1.push(parent);

        Production currentProduction = p;
        while (currentProduction.isCompoundProduction()) {
            CompoundProduction compoundCurrentP = (CompoundProduction) currentProduction;

            TreeNode[] nodesParent = new TreeNode[compoundCurrentP.getRight().length - compoundCurrentP.child.getRight().length + 1];
            TreeNode[] nodesChild = new TreeNode[compoundCurrentP.child.getRight().length];

            System.arraycopy(nodes, 0, nodesParent, 0, compoundCurrentP.position);
            System.arraycopy(nodes, compoundCurrentP.position + compoundCurrentP.child.getRight().length, nodesParent, compoundCurrentP.position + 1, nodesParent.length - compoundCurrentP.position - 1);

            Stack<TreeNode> children1 = new Stack<TreeNode>();
            nodesParent[compoundCurrentP.position] = new TreeNode(new AttributedSymbol(compoundCurrentP.child.getLeft()), children1, parent, compoundCurrentP.child.get());

            for (int i = 0; i < nodesParent.length; i++) {
                children.push(nodesParent[i]);
                nodesParent[i].setParent(parent);
            }

            System.arraycopy(nodes, compoundCurrentP.position, nodesChild, 0, nodesChild.length);

            currentProduction = compoundCurrentP.child;
            nodes = nodesChild;
            children = children1;
            parent = new TreeNode(new AttributedSymbol(currentProduction.getLeft()), children, null, currentProduction.get());
        }


        for (int i = 0; i < currentProduction.getRight().length; i++) {
            //assert (right[right.length - i - 1].getName().equals(stack1.peek().getSymbol().getSymbol().getName())) : i + " " +right[i].getName() + " " + stack1.peek().getSymbol().getSymbol().getName();
            children.push(nodes[i]);
            nodes[i].setParent(parent);
        }

        return parent1;
    }
}
